
#include <BOpenGL>
#include "ITextBlock.h"
#include "member_BTexture.h"

using namespace BWE;

ITextBlock::ITextBlock()
{
	visible = true;
	align = Align_Left;
	wordWrap = true;
	lineSize = 0;
	lineSpacing = 0;

	dirty = true;
	inputIndex = 0;
	inputCount = 0;
}
ITextBlock::ITextBlock(const ITextBlock& other)
{
	visible = other.visible;
	align = other.align;
	wordWrap = other.wordWrap;
	lineSize = other.lineSize;
	lineSpacing = other.lineSpacing;

	dirty = true;
	inputIndex = 0;
	inputCount = 0;
}
ITextBlock::~ITextBlock()
{

}

void ITextBlock::setStyle(const BStyle* style)
{
	if (_style != style)
	{
		_style = style;
		if (_format && _style)
			_format->styled(_style);
		if (_format && text.size())
			_format->changed(text);
		dirty = true;
	}
}
void ITextBlock::setFormat(BTextFormat* format)
{
	if (_format != format)
	{
		_format = format;
		if (_format && _style)
			_format->styled(_style);
		if (_format && text.size())
			_format->changed(text);
		dirty = true;
	}
}
BTextFormat* ITextBlock::format()
{
	return _format;
}

void ITextBlock::flushText()
{
	text.reset();
	for (int i = 0; i < cells.size(); i++)
	{
		text.append(cells[i].code);
	}
	if (_format)
		_format->changed(this->text);
}

void ITextBlock::reset()
{
	if (cells.size() > text.size() * 4)
		cells.clear();
	else
		cells.reset();
	const char* cstr = text.cstr();
	for (auto it = text.begin(); it.valid(); ++it)
	{
		ICodeCell& cell = cells.append();
		cell.code = *it;
	}
	inputIndex = 0;
	inputCount = 0;
	dirty = true;
	if (_format)
		_format->changed(text);
}
void ITextBlock::reset(const BString& text)
{
	this->text = text;
	reset();
}
bool ITextBlock::remove(int pos, int count)
{
	if (!cells.remove(pos, count))
		return false;
	dirty = true;
	flushText();
	return true;
}
void ITextBlock::append(const BString& text)
{
	this->text << text;
	for (auto cit = text.begin(); cit.valid(); ++cit)
	{
		cells.append().code = *cit;
	}
	dirty = true;
}
bool ITextBlock::insert(const BCode& code)
{
	if (!cells.insert(inputIndex, ICodeCell()))
		return false;
	ICodeCell& cell = cells[inputIndex];
	cell.code = code;
	inputIndex++;
	dirty = true;
	flushText();
	return true;
}
bool ITextBlock::insert(const BString& text)
{
	int count = text.count();
	if (!cells.insert(inputIndex, ICodeCell(), count))
		return false;
	for (auto it = text.begin(); it.valid(); it++)
	{
		ICodeCell& cell = cells[inputIndex++];
		cell.code = *it;
	}
	dirty = true;
	flushText();
	return true;
}

void ITextBlock::substr(BString& str, int pos, int count) const
{
	if (pos < 0 || pos >= cells.size())
		return;
	if (count < 1)
		return;
	int stop = pos + count;
	if (stop > cells.size())
		return;
	for (int i = pos; i < stop; i++)
	{
		str.append(cells[i].code);
	}
}
void ITextBlock::selection(BString& str) const
{
	if (inputIndex < 0 || inputIndex >= cells.size())
		return;
	if (inputCount < 1)
		return;
	int stop = inputIndex + inputCount;
	if (stop > cells.size())
		return;
	for (int i = inputIndex; i < stop; i++)
	{
		str.append(cells[i].code);
	}
}

bool ITextBlock::selectWord()
{
	if (cells.empty())
		return false;

	if (!cells.check(inputIndex))
		return false;

	bool large = cells[inputIndex].code.isLarge();
	bool space = cells[inputIndex].code.isSpace();
	bool punct = cells[inputIndex].code.isPunct();
	bool alpha = cells[inputIndex].code.isAlpha();

	int begin = inputIndex;
	while (begin >= 0)
	{
		const BCode& code = cells[begin].code;
		if (large != code.isLarge())
			break;
		if (space != code.isSpace())
			break;
		if (punct != code.isPunct())
			break;
		if (alpha != code.isAlpha())
			break;
		begin--;
	}
	begin++;
	int end = begin;
	while (end < cells.size())
	{
		const BCode& code = cells[end].code;
		if (large != code.isLarge())
			break;
		if (space != code.isSpace())
			break;
		if (punct != code.isPunct())
			break;
		if (alpha != code.isAlpha())
			break;
		end++;
	}
	inputIndex = begin;
	inputCount = end - begin;
	return inputCount > 0;
}
bool ITextBlock::removeSelection()
{
	if (inputIndex >= 0 && inputCount > 0)
	{
		cells.remove(inputIndex, inputCount);
		inputCount = 0;
		dirty = true;
		flushText();
		return true;
	}
	return false;
}

bool ITextBlock::seekHead()
{
	if (inputIndex != 0 || inputCount != 0)
	{
		inputIndex = 0;
		inputCount = 0;
		return true;
	}
	return false;
}
bool ITextBlock::seekPoint(const BPoint& point)
{
	int index = indexAt(point);
	if (inputIndex != index || inputCount != 0)
	{
		inputIndex = index;
		inputCount = 0;
		return true;
	}
	return false;
}
bool ITextBlock::seekTail()
{
	int tail = cells.size();
	if (cells.last().code == '\n')
		tail = cells.size() - 1;
	if (inputIndex != tail || inputCount != 0)
	{
		inputIndex = tail;
		inputCount = 0;
		return true;
	}
	return false;
}

int ITextBlock::seekPrevCode()
{
	inputIndex--;
	inputCount = 0;
	return inputIndex;
}
int ITextBlock::seekNextCode()
{
	inputIndex++;
	inputCount = 0;
	return inputIndex;
}
int ITextBlock::seekPrevWord()
{
	inputCount = 0;
	bool found = false;
	int begin = inputIndex;
	while (--begin > 0)
	{
		const BCode& code = cells[begin].code;
		if (code > 255 || !code.isSpace())
		{
			found = true;
			continue;
		}
		if (found && code.isSpace())
		{
			begin++;
			break;
		}
	}
	inputIndex = begin;
	return inputIndex;
}
int ITextBlock::seekNextWord()
{
	int size = cells.size();
	int begin = inputIndex;
	while (begin < size)
	{
		const BCode& code = cells[begin].code;
		if (code < 255)
		{
			if (code.isSpace())
				break;
		}
		begin++;
	}
	while (begin < size)
	{
		const BCode& code = cells[begin].code;
		if (code < 255)
		{
			if (!code.isSpace())
				break;
		}
		begin++;
	}
	inputIndex = begin;
	return inputIndex;
}

int ITextBlock::indexAt(const BPoint& pos) const
{
	if (pos.y() < this->top())
		return -1;
	if (pos.y() > this->bottom())
		return cells.size();
	BPoint point = pos - this->pos();
	int index = 0;
	while (index < cells.size())
	{
		BRect rect = cells[index].rect;
		if (rect.bottom() < point.y())
		{
			index++;
			continue;
		}
		if (rect.top() > point.y())
			break;
		if (rect.center().x() > point.x())
			break;
		index++;
	}
	return index;
}
int ITextBlock::wordWidth(int pos, int& end) const
{
	const ICodeCell& prev = cells.at(pos - 1);
	if (!prev.code.isSpace() && !prev.code.isPunct())
		return 0;
	const ICodeCell& cell = cells[pos];
	if (cell.code.isSpace())
		return 0;
	int width = cell.rect.width();
	const char* ustr = cell.code.str();
	if (cell.code.isSpace())
		return width;
	if (cell.code.isPunct())
		return width;
	if (cell.code.num() > 1)
		return width;
	end = pos;
	while (++end < cells.size())
	{
		const BCode& code = cells[end].code;
		if (code.isSpace())
			break;
		if (code.isPunct())
			break;
		if (code.num() > 1)
			break;
		width += cells[end].rect.width();
	}
	return width;
}

void ITextBlock::adjust(int pageWidth)
{
	if (dirty && font)
	{
		if (_format)
		{
			if (lineSpacing < _format->lineSpacing(0))
				lineSpacing = _format->lineSpacing(0);
		}
		rowHeights.reset();
		rowHeights.append(font ? font->size() : 16);
		int row = 0;
		const BFont* realFont = font;
		for (int i = 0; i < cells.size(); i++)
		{
			ICodeCell& cell = cells[i];
			if (_format)
			{
				if (const BFont* font = _format->font(i))
					realFont = font;
				if (rowHeights[row] < _format->lineHeight(row))
					rowHeights[row] = _format->lineHeight(row);
			}
			if (rowHeights[row] < realFont->size())
				rowHeights[row] = realFont->size();
			if (rowHeights[row] < lineSize)
				rowHeights[row] = lineSize;
			if (cell.code == '\n')
			{
				rowHeights.append();
				row++;
			}
			if (realFont)
				cell.fresh(realFont);
		}
		
		row = 0;
		int lineHeight = rowHeights.first();
		int width = 0;
		int height = lineHeight;
		int up = (int)(lineHeight * 0.2f);
		int x = 0;
		int y = 0;
		int wordEnd = 0;
		for (int i = 0; i < cells.size(); i++)
		{
			ICodeCell& cell = cells[i];
			int codeWidth = cell.rect.width();
			if (codeWidth < 1)
				continue;
			if (cell.code == '\n')
			{
				cell.rect.setPos(x, y);
				cell.rect.setHeight(lineHeight);
				cell.image_rect.setPos(x + cell.image_left, cell.rect.bottom() - cell.image_top - up);
				x += codeWidth;
				if (width < x)
					width = x;
				x = 0;
				y += lineHeight + lineSpacing;
				row++;
				lineHeight = rowHeights[row];
				continue;
			}
			if (pageWidth > 0)
			{
				if (codeWidth < pageWidth && x + codeWidth > pageWidth)
				{
					x = 0;
					y += lineHeight + lineSpacing;
				}
				else if (wordWrap && x > 0 && i > wordEnd)
				{
					int ww = wordWidth(i, wordEnd);
					if (x + ww > pageWidth)
					{
						x = 0;
						y += lineHeight + lineSpacing;
					}
				}
			}
			if (cell.code == '\t')
			{
				int times = x / codeWidth + 1;
				int width = times * codeWidth - x;
				cell.rect.setPos(x, y);
				cell.rect.setSize(width, lineHeight);
				cell.image_rect = cell.rect;
				x = times * codeWidth;
			}
			else
			{
				cell.rect.setPos(x, y);
				cell.rect.setHeight(lineHeight);
				cell.image_rect.setPos(x + cell.image_left, cell.rect.bottom() - cell.image_top - up);
				x += codeWidth;
			}
			if (width < x)
				width = x;
			if (height < y + lineHeight)
				height = y + lineHeight + lineSpacing;
		}
		if (rowHeights.size() > 1)
		{
			height -= lineSpacing;
		}
		this->setSize(width, height);
		if (pageWidth > width)
		{
			int xoffset = 0;
			if (align & Align_Right)
				xoffset = pageWidth - width;
			if (align & Align_HCenter)
				xoffset = (pageWidth - width) / 2;
			if (xoffset > 0)
			{
				for (int i = 0; i < cells.size(); i++)
				{
					ICodeCell& cell = cells[i];
					cell.rect.move(xoffset, 0);
					cell.image_rect.move(xoffset, 0);
				}
			}
		}
		dirty = false;
	}
}
void ITextBlock::paint(BPainter& painter) const
{
	BColor textColor = painter.color();
	BPoint pos = this->pos();
	for (int i = 0; i < cells.size(); i++)
	{
		const ICodeCell& cell = cells[i];
		if (cell.code.utf() == 16)
		{
			painter.setColor(255, 0, 0, 128);
			painter.fillRect(cell.rect + pos);
		}
	}
	painter.setColor(textColor);
	glEnable(GL_TEXTURE_2D);
	glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE);
	for (int i = 0; i < cells.size(); i++)
	{
		const ICodeCell& cell = cells[i];
		BRect rect = cell.rect + pos;
		if (cell.image.empty())
			continue;
		if (_format)
		{
			if (const BColor* color = _format->color(i))
				glColor4ub(color->r(), color->g(), color->b(), color->a());
			else
				glColor4ub(textColor.r(), textColor.g(), textColor.b(), textColor.a());
		}
		BRect image_rect = cell.image_rect + pos;
		int x = image_rect.x();
		int y = image_rect.y();
		int w = image_rect.width();
		int h = image_rect.height();
		if (texture_member(cell.image)->dirty)
			cell.image->flush();
		glBindTexture(GL_TEXTURE_2D, texture_member(cell.image)->tbo);
		glBegin(GL_QUADS);
		glTexCoord2f(0.0f, 0.0f);		glVertex2i(x, y);
		glTexCoord2f(0.0f, 1.0f);		glVertex2i(x, y + h);
		glTexCoord2f(1.0f, 1.0f);		glVertex2i(x + w, y + h);
		glTexCoord2f(1.0f, 0.0f);		glVertex2i(x + w, y);
		glEnd();
		glBindTexture(GL_TEXTURE_2D, 0);
	}
	glDisable(GL_TEXTURE_2D);
}
